require 'cgi'


def el *a
  Element.get(*a)
end


class Element < MyComposite
  @@pair_tags = %w{a b div em i iframe label option select span script textarea}

  attr_accessor :tag_name, :parent

  def self.factory tag, klass
    (@@factory ||= {}).merge!(tag => klass)
  end

  def self.get *a
    (@@factory[a[0]] || Element).new(*a)
  end

  def initialize tag, content = nil, attrs = {}
    super()
    if attrs.keys.length == 0 && (content.is_a? Hash)
      attrs = content
      content = nil
    end
    self.tag_name = tag
    #@content = []
    if content
      self << content
    end

    @attributes = {}
    @rendered_attributes = {}
    self.attributes= attrs
  end

  def clone
    Element.new(tag_name, children.dup, @attributes)
  end

  def tag_name= tag
    @tag_name = tag.to_s if tag
  end

  def attributes= attrs
    attrs.each {|k, v|
      next unless v
      set(k, v)
    }
  end

  def append_child child
    self << child
  end

  def << something
    super
    #append_child something
    self
  end

#  def children
#    @content
#  end

#  def children= ch
#    self.child_nodes = ch
#  end
  
#  def child_nodes= nodes
#    @content = nodes
#  end

  def render_content
    children.join()
  end

  def open_tag
   return '' if @tag_name.nil?
   if !pair_tag?
      '<'+@tag_name+render_attrs+'/>'
    else
      '<'+@tag_name+render_attrs+'>'
    end
  end

  def close_tag
    return '' if @tag_name.nil?
    if !pair_tag?
      ''
    else
      '</'+@tag_name+'>'
    end
  end

  def pair_tag?
    @@pair_tags.include?(tag_name) || @children.length > 0
  end

  def render_attrs
    @rendered_attributes.collect { |k, v|
      if v != nil
        " #{k}=\"#{v}\""
      end
    }.join()
  end

  def respond_to? name
    return true if super(name)
    name = name.to_s
    if name[-1,1] == '='
      (@attributes.has_key? name[0, name.length-1].to_sym)
    else
      (@attributes.has_key? name.to_sym)
    end
  end

  def method_missing name, *args
    orig_name = name
    name = name.to_s
    if name[-1,1] == '='
      attr = name[0, name.length-1]
      #if @attributes.has_key? attr
        return set(attr, *args)
      #end
    else
      #if @attributes.has_key? name
        return get(name)
      #end
    end
    super(orig_name, *args)
  end

  def set attr_name, value
    @attributes[attr_name.to_sym] = value
    @rendered_attributes[attr_name.to_sym] = CGI.escapeHTML(value.to_s)
  end

  def set_attribute_raw attr, data
    set_raw attr, data
  end

  def set_raw attr, data
    @attributes[attr.to_sym] = data
    @rendered_attributes[attr.to_sym] = data
  end

  def get attr_name
    @attributes[attr_name.to_sym]
  end

  def id
    get('id') || set('id', self.class.generate_id)
  end

  def id= v
    set 'id', v
    v
  end

  def self.generate_id
    'e'+(rand()*10000000000).round.to_s(32)
  end

  def class_name
    @attributes['class']
  end

  def class_name= class_name
    @attributes['class'] = class_name
  end

  def to_s
    open_tag+render_content+close_tag
  end

  def inner_html
    render_content
  end

#  def visit &block
#    block.call(self)
#    children.each { |ch|
#      if ch && ch.respond_to?(:visit)
#        ch.visit(&block)
#      end
#    }
#  end

  def dump
    data = {}
    visit { |el|      
      if el.respond_to? :name
        data[el.name] = el.value
      end      
    }
    data
  end

  def recieve data
    #p "recieve #{self}"
    visit { |el|
      #p "visiting #{el} #{el.get('name')}"
      if el.respond_to?(:name) && data.has_key?(el.name)
        #p "-- setting #{data[el.name]} to #{el}"
        el.value = data[el.name]
        :skip_children
      end
    }
  end

#  def recieve_data data
#    if tag_name == 'input'
#      self.value = data
#    elsif tag_name == 'select'
#       p "#{self} #{self.tag_name}"
#       children.each { |ch|
#            if (ch.get('value') || ch.inner_html) == data
#              ch.selected = "selected"
#            end
#         }
#    end
#  end

  class Test < FwTest
    def test_render_non_paired_tag
      e = Element.new 'br'
      assert_equal '<br/>', e.to_s
    end

    def test_render_tag_with_content
      e = Element.new :h1, "abcd"
      assert_equal '<h1>abcd</h1>', e.to_s
    end

    def test_render_pair_tag_with_attributes
      e = Element.new :textarea, "abcd"
      e.rows = 5
      assert_equal '<textarea rows="5">abcd</textarea>', e.to_s
    end

    def test_render_non_paired_tag_with_attributes
      e = Element.new :br
      e.style = 'clear:both'
      assert_equal '<br style="clear:both"/>', e.to_s
    end

    def test_element_nesting
      div = Element.new :div
      span = Element.new :span, "abc"
      div.append_child span
      assert_equal '<div><span>abc</span></div>', div.to_s
    end

    def test_constructor_with_attribues
      e = Element.new :br, :style => 'clear:both'
      assert_equal '<br style="clear:both"/>', e.to_s
    end

    def test_constructor_with_attribues
      e = Element.new :span, "abc", :style => 'clear:both'
      assert_equal '<span style="clear:both">abc</span>', e.to_s
    end

    def test_get_attr_with_nil_value
      e = Element.new :span
      assert_equal nil, e.style
    end

    def test_get_attr_with_value
      e = Element.new :span, :style => "color:red"
      assert_equal "color:red", e.style
    end

    def test_element_attrs_are_escaped
      e = Element.new :span, :style => "<"
      assert_match(/"&lt;"/, e.to_s)
    end

    def test_element_attrs_are_to_stringed
      e = Element.new :span, :style => 5
      assert_match(/"5"/, e.to_s)
    end

    def test_attribute_names_are_stringified
      e = Element.new(:span, "aaa", :name => 'hello', 'name' => 'world')
      eq e.name, "world"
    end

    def test_attribute_names_are_stringified
      e = Element.new(:span, "aaa", :name => 'hello', 'name' => 'world')
      eq e.to_s, "<span name=\"world\">aaa</span>"
    end

    def test_respond_to_dynamic_attributes
      e = Element.new(:span, :my_attr => 'value')
      assert e.respond_to? :my_attr
      assert e.respond_to? 'my_attr'
    end

#    def test_element_append_child_sets_parent
#      parent = Element.new :parent
#      child = parent.append_child Element.new(:child)
#      assert_equal child.parent, parent
#    end
  end

end
